package com.iot.modbus.execute;

import cn.hutool.core.util.ObjectUtil;
import com.iot.modbus.event.pojo.DeviceMessage;
import com.iot.modbus.event.subject.MessageSubject;
import com.iot.modbus.event.pojo.MetricMessage;
import com.iot.modbus.execute.config.ConfigDataType;
import com.iot.modbus.execute.config.DeviceDataConfig;
import com.iot.modbus.execute.config.MetricDataConfig;
import com.iot.modbus.manager.TaskManager;
import com.iot.modbus.route.ReaderSessionRoute;
import com.iot.modbus.route.impl.SimpleReaderSessionRoute;
import com.serotonin.modbus4j.ip.IpParameters;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 采集执行器
 */
public class Execute {

    private static final ReaderSessionRoute route = new SimpleReaderSessionRoute();
    private static final ReaderSessionRoute routeProxy = (ReaderSessionRoute) Proxy.newProxyInstance(ReaderSessionRoute.class.getClassLoader(),new Class<?>[]{ReaderSessionRoute.class},new ExecuteInterceptor());
    private static final Object lock = new Object();
    private static final Map<String, DeviceDataConfig> deviceDataConfigMap = new ConcurrentHashMap<>();
    /**
     * 添加任务
     *  并集
     */
    public static void add(DeviceDataConfig deviceDataConfig){
        synchronized (lock) {
            String key = key(deviceDataConfig);
            if (deviceDataConfigMap.containsKey(key)){
                List<MetricDataConfig> metricDataConfigs = deviceDataConfig.getMetricDataConfigs();
                DeviceDataConfig deviceDataConfig1 = deviceDataConfigMap.get(key);
                //并集
                metricDataConfigs.addAll(deviceDataConfig1.getMetricDataConfigs());
            }
            deviceDataConfigMap.put(key,deviceDataConfig);
            TaskManager.edit(key,runnable(deviceDataConfig),deviceDataConfig.getDelay());
        }
    }

    /**
     * 删除任务
     * 差集
     * @param deviceDataConfig
     */
    public static void remove(DeviceDataConfig deviceDataConfig){
        synchronized (lock) {
            String key = key(deviceDataConfig);
            if (deviceDataConfigMap.containsKey(key)){
                DeviceDataConfig deviceDataConfig1 = deviceDataConfigMap.get(key);
                List<MetricDataConfig> metricDataConfigs = deviceDataConfig1.getMetricDataConfigs();
                //差集
                metricDataConfigs.removeAll(deviceDataConfig.getMetricDataConfigs());
                deviceDataConfigMap.put(key,deviceDataConfig1);
                TaskManager.edit(key,runnable(deviceDataConfig1),deviceDataConfig.getDelay());
            }



        }

    }

    /**
     * 更新任务
     * @param deviceDataConfig
     */
    public static void update(DeviceDataConfig deviceDataConfig){
        synchronized (lock) {
            String key = key(deviceDataConfig);
            if (deviceDataConfigMap.containsKey(key)) {
                DeviceDataConfig deviceDataConfig1 = deviceDataConfigMap.get(key);
                List<MetricDataConfig> metricDataConfigs = deviceDataConfig1.getMetricDataConfigs();
                metricDataConfigs.removeAll(deviceDataConfig1.getMetricDataConfigs());
                List<MetricDataConfig> metricDataConfigs1 = deviceDataConfig.getMetricDataConfigs();
                metricDataConfigs1.addAll(metricDataConfigs);
            }

            TaskManager.edit(key(deviceDataConfig),runnable(deviceDataConfig),deviceDataConfig.getDelay());
        }

    }

    public static Runnable runnable(final DeviceDataConfig deviceDataConfig){
        IpParameters ipParameters = deviceDataConfig.getIpParameters();
        return () ->{
            List<MetricDataConfig> metricDataConfigs = deviceDataConfig.getMetricDataConfigs();
            DeviceMessage deviceMessage = new DeviceMessage(deviceDataConfig.getDeviceId(), deviceDataConfig.getDeviceName());
            deviceMessage.setMetrics(
                    metricDataConfigs.stream()
                            .map(metricDataConfig -> {
                                ConfigDataType dataType = metricDataConfig.getDataType();
                                MetricMessage metricMessage = extracted(metricDataConfig, dataType, ipParameters);
                                if (metricMessage == null) {
                                    return null;
                                }
                                return  metricMessage;
                            })
                            .filter(ObjectUtil::isNotNull)
                            .collect(Collectors.toList()));
            if (ObjectUtil.isNotEmpty(deviceMessage.getMetrics())) {
                MessageSubject.onMessage(deviceMessage);
            }
        };
    }

    private static MetricMessage extracted(MetricDataConfig metricDataConfig, ConfigDataType dataType, IpParameters ipParameters) {
        MetricMessage metricMessage = new MetricMessage();
        metricMessage.setMetricId(metricDataConfig.getMetricId());
        metricMessage.setMetricName(metricDataConfig.getMetricName());
        metricMessage.setTimestamp(new Date());
        metricMessage.setMetricUnit(metricDataConfig.getMetricUnit());
        switch (dataType){
            case SHORT_ARRAY: {
                short[] value = routeProxy.read(ipParameters, metricDataConfig.getStartOffset(),metricDataConfig.getNumberOfRegisters());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(Arrays.toString(value));
            }
            break;
            case UINT16:{
                Integer value = routeProxy.readUInt16(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;
            case INT16:{
                Short value = routeProxy.readInt16(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;
            case UINT32:{
                Long value = routeProxy.readUInt32(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;
            case INT32:{
                Integer value = routeProxy.readInt32(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;
            case FLOAT32:{
                Float value = routeProxy.readFloat32(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;
            case FLOAT64:{
                Double value = routeProxy.readFloat64(ipParameters, metricDataConfig.getStartOffset(), metricDataConfig.getByteOrder());
                if (value == null) {
                    return null;
                }
                metricMessage.setValue(String.valueOf(value));
            }
            break;

            default:

                break;
        }
        return metricMessage;
    }


    private static String key(final DeviceDataConfig deviceDataConfig){
        return deviceDataConfig.getDeviceName() +"@"+ deviceDataConfig.getIpParameters().getHost()+"@"+deviceDataConfig.getIpParameters().getPort();
    }

    private static class ExecuteInterceptor implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {

            try {
                return method.invoke(route,args);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.fillInStackTrace();
                return null;
            }

        }
    }
}
